    ; 主引导扇区代码
    ; @author: Milon
    ; @date: 2024-04-15

%include "./common/global_defs.h"

section mbr vstart=0x7c00
        xor ax, ax
        mov ds, ax
        mov es, ax
        mov ss, ax
        mov sp, 0x7c00

        ; 利用BIOS中断读内核加载器的第一个扇区
        ; 以下在栈中构造相关信息地址结构
        push dword 0x00000000
        push dword LDR_START_SECTOR             ; 压入起始逻辑扇区号
        push word LDR_PHY_ADDR >> 4             ; 压入数据缓冲区逻辑段地址
        push word 0x0000                        ;     数据缓冲区段内偏移
        push word 0x0001                        ; 压入传输扇区数
        push word 0x0010                        ; 压入地址结构尺寸
    
        mov ah, 0x42                            ; 指定功能号（读硬盘扇区）
        mov si, sp                              ; 使得DS:SI执行上面构造的信息地址结构
        mov dl, 0x80                            ; 指定驱动器号，0x80为第一块硬盘
        int 0x13                                ; 如果读取正常CF=0，AH=0；如果失败CF=1，AH=错误代码

        mov di, msg1 - msg0
        mov bp, msg0
        jc error_handle                         ; 读取硬盘错误，则跳转

        ; 以下判断加载器是否合法
        push ds
        mov ax, LDR_PHY_ADDR >> 4
        mov ds, ax
        cmp dword [0x00], 'Miln'                ; loader的头4个字节应当是Miln的ASCII编码   
        mov di, mend - msg1
        mov bp, msg1
        jnz error_handle

        ; 以下判断加载器大小
        mov eax, [0x04]
        xor edx, edx
        mov ebx, 512
        div ebx
        or edx, edx                             ; 判断余数是否为0
        jnz @1                                  ; 如果余数不等于0，则剩余扇区数在eax中
        dec eax                                 ; 如果余数为0，则剩余扇区-1
    @1:
        or eax, eax
        jz go_ldr                               ; 如果剩余扇区数=0，直接跳转，否则继续读取剩余扇区
        pop ds
        mov [si + 0x02], eax
        mov word [si + 0x04], 512
        inc dword [si + 0x08]
        mov ah, 0x42
        mov dl, 0x80
        int 0x13
        mov di, msg1 - msg0
        mov bp, msg0
        jc error_handle                         ; 读取硬盘错误，则跳转

    go_ldr:
        mov sp, 0x7c00                          ; 恢复栈初始状态
        mov ax, LDR_PHY_ADDR >> 4               ; 数据段寄存器指向加载器起始物理地址的逻辑段地址
        mov ds, ax
        mov es, ax

        push ds                                 ; 压入加载器逻辑段地址
        push word [0x08]                        ; 压入加载器入口点段内偏移地址
        retf                                    ; 通过远过程返回的方式跳转至加载器执行



    error_handle:                               ; 错误处理代码
                                                ; 输入：di=字符串长度
                                                ;       es:bp=字符串所在逻辑段地址:段内偏移量
        ; 以下通过BIOS中断获取光标位置到dh:dl
        mov ah, 0x03                            ; 功能号0x03读光标位置
        mov bh, 0x00                            ; 页号，文本模式可置0
        int 0x10

        mov ah, 0x13                            ; 功能号0x13，显示字符串
        mov al, 0x01                            ; 带光标跟随的写屏，属性在BL中
        mov bh, 0x00
        mov bl, 10000000B                       ; 红底黑字
        mov cx, di                              ; 因为上一步ch:cl会返回光标起始扫描线:结束扫描线，所以用di传入字符串长度
        int 0x10

        cli
        hlt

    
    msg0             db "Disk error.",0x0d,0x0a
    msg1             db "Missing loader.",0x0d,0x0a
    mend:

    times 510 - ($ - $$) db 0
    db 0x55, 0xaa